; 在保护模式下，定义30h中断，实现原实模式下的10h号BIOS中断的13h号功能（显示字符串）
; 并在pmtest3.asm程序中添加相关的中断调用代码段来测试效果

; ==========================================
; pmtest3.asm
; 编译方法：nasm pmtest3.asm -o pmtest3.com
; ==========================================

%include    "pm.inc"    ; 常量, 宏, 以及一些说明

PageDirBase0        equ 200000h ; 页目录开始地址:  2M
PageTblBase0        equ 201000h ; 页表开始地址:       2M + 4K

delay equ 1000              ; 计时器延迟计数初值（应该=100？）

org 0100h
    jmp LABEL_BEGIN

[SECTION .gdt]
; GDT
;                               段基址, 段界限, 属性
LABEL_GDT:          Descriptor  0,      0,  0   ; 空描述符
LABEL_DESC_NORMAL:  Descriptor  0,   0ffffh,  DA_DRW ; Normal 描述符
LABEL_DESC_FLAT_C:  Descriptor  0,   0fffffh,  DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G
LABEL_DESC_FLAT_RW: Descriptor  0,   0fffffh,  DA_DRW | DA_LIMIT_4K ; 0 ~ 4G
LABEL_DESC_CODE32:  Descriptor  0,  SegCode32Len - 1, DA_CR | DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE16:  Descriptor  0,   0ffffh,  DA_C  ; 非一致代码段, 16
LABEL_DESC_DATA:        Descriptor  0,   DataLen - 1,  DA_DRW   ; Data
LABEL_DESC_STACK:   Descriptor  0,  TopOfStack,  DA_DRWA | DA_32    ; Stack, 32 位
LABEL_DESC_VIDEO:   Descriptor 0B8000h, 0ffffh, DA_DRW  ; 显存首地址
; GDT 结束

GdtLen  equ $ - LABEL_GDT   ; GDT长度
GdtPtr  dw  GdtLen - 1      ; GDT界限
        dd  0               ; GDT基地址

; GDT 选择符
SelectorNormal  equ LABEL_DESC_NORMAL   - LABEL_GDT
SelectorFlatC       equ LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW  equ LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorCode32  equ LABEL_DESC_CODE32   - LABEL_GDT
SelectorCode16  equ LABEL_DESC_CODE16   - LABEL_GDT
SelectorData        equ LABEL_DESC_DATA     - LABEL_GDT
SelectorStack       equ LABEL_DESC_STACK        - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO        - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]     ; 数据段
ALIGN   32
[BITS   32]
LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_szPMMessage:   db  "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 进入保护模式后显示此字符串
_szMemChkTitle: db  "BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0 ; 同上
_szRAMSize          db  "RAM size:", 0
_testStr:           db  "I am a String!"; add
testStrLen         equ  $ - _testStr
_szReturn           db  0Ah, 0
; 变量
_count dw delay     ; 计时器计数变量，初值=delay
_dwMCRNumber:       dd  0   ; Memory Check Result
_dwDispPos:         dd  (80 * 6 + 0) * 2    ; 屏幕第 6 行, 第 0 列。
_dwMemSize:         dd  0
_ARDStruct: ; Address Range Descriptor Structure
    _dwBaseAddrLow:     dd  0
    _dwBaseAddrHigh:        dd  0
    _dwLengthLow:       dd  0
    _dwLengthHigh:      dd  0
    _dwType:                dd  0
_PageTableNumber:       dd  0
_MemChkBuf: times   256 db  0

; 保护模式下使用这些符号
szPMMessage     equ _szPMMessage    - $$
szMemChkTitle   equ _szMemChkTitle  - $$
szRAMSize       equ _szRAMSize  - $$
szReturn            equ _szReturn   - $$
dwDispPos       equ _dwDispPos  - $$
dwMemSize       equ _dwMemSize  - $$
dwMCRNumber equ _dwMCRNumber    - $$
ARDStruct       equ _ARDStruct  - $$
    dwBaseAddrLow   equ _dwBaseAddrLow  - $$
    dwBaseAddrHigh  equ _dwBaseAddrHigh - $$
    dwLengthLow     equ _dwLengthLow    - $$
    dwLengthHigh    equ _dwLengthHigh   - $$
    dwType      equ _dwType     - $$
MemChkBuf       equ _MemChkBuf  - $$
PageTableNumber equ _PageTableNumber- $$
testStr         equ _testStr  - $$ ; add

count equ _count - $$   ; 在时钟中断处理中，用于延时的变量

DataLen         equ $ - LABEL_DATA
; END of [SECTION .data1]


; IDT
[SECTION .idt]
ALIGN   32
[BITS   32]
LABEL_IDT:
; 门               目标选择符,            偏移, DCount, 属性
%rep 32
        Gate  SelectorCode32,  SpuriousHandler,  0, DA_386IGate
%endrep
.020h:  Gate  SelectorCode32,  ClockHandler,     0, DA_386IGate
%rep 15
        Gate  SelectorCode32,  SpuriousHandler,  0, DA_386IGate
%endrep
.030h:  Gate  SelectorCode32,  DispStrHandler,   0, DA_386IGate ; add
%rep 79
        Gate  SelectorCode32,  SpuriousHandler,  0, DA_386IGate
%endrep
.080h:  Gate  SelectorCode32,  UserIntHandler,   0, DA_386IGate

IdtLen  equ $ - LABEL_IDT
IdtPtr  dw  IdtLen - 1  ; 段界限
        dd  0       ; 基地址（后面再赋值）
; END of [SECTION .idt]


; 全局堆栈段
[SECTION .gs]
ALIGN   32
[BITS   32]
LABEL_STACK:
    times 512 db 0

TopOfStack  equ $ - LABEL_STACK - 1

; END of [SECTION .gs]


[SECTION .s16]
[BITS   16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    ; 得到内存数
    mov ebx, 0
    mov di, _MemChkBuf
.loop:
    mov eax, 0E820h
    mov ecx, 20
    mov edx, 0534D4150h
    int 15h
    jc  LABEL_MEM_CHK_FAIL
    add di, 20
    inc dword [_dwMCRNumber]
    cmp ebx, 0
    jne .loop
    jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
    mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:

    ; 初始化 32 位代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32
    mov word [LABEL_DESC_CODE32 + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_CODE32 + 4], al
    mov byte [LABEL_DESC_CODE32 + 7], ah

    ; 初始化数据段描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_DATA
    mov word [LABEL_DESC_DATA + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_DATA + 4], al
    mov byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_STACK
    mov word [LABEL_DESC_STACK + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_STACK + 4], al
    mov byte [LABEL_DESC_STACK + 7], ah

    ; 为加载 GDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT      ; eax <- gdt 基地址
    mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址

    ; 为加载 IDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_IDT      ; eax <- idt 基地址
    mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 加载 IDTR
    lidt    [IdtPtr]

    ; 打开地址线A20
    in  al, 92h
    or  al, 00000010b
    out 92h, al

    ; 置PE位，准备切换到保护模式
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    ; 真正进入保护模式
    jmp dword SelectorCode32:0      ; 执行这一句会把SelectorCode32装入cs, 
                                ; 并跳转到Code32Selector:0处

; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS   32]

LABEL_SEG_CODE32:
    call    SetupPaging

    mov ax, SelectorData
    mov ds, ax          ; 数据段选择符
    mov es, ax
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择符

    mov ax, SelectorStack
    mov ss, ax          ; 堆栈段选择符
    mov esp, TopOfStack

    call Init8259A ; 初始化中断控制器，启动时钟中断
    call SetTimer   ; 设置时钟中断的时间间隔为10ms
    sti         ; 打开中断

    int 80h     ; 调用用户中断处理程序（显示‘I’）
    int 40h     ; 调用伪中断处理程序（显示‘!’）

    ; 下面显示一个字符串"In Protect Mode now. ^-^"
    push    szPMMessage
    call    DispStr
    add esp, 4

    ; 显示内存信息的表头"BaseAddrL BaseAddrH LengthLow LengthHigh   Type"
    push    szMemChkTitle
    call    DispStr
    add esp, 4

    ; add 调试30h中断显示字符串
    ; mov     ebp, testStr
    ; mov     cx,  testStrLen
    mov     bl,  0ch                ; 不闪0黑底000亮红字1100
    mov     esi, testStr
    mov     cx,  testStrLen
    mov     edx, 0288h ;(80 * 4 + 4) * 2 ; 屏幕第 4 行, 第 4 列; [dwDispPos]
    int 30h
    ; add     edx, testStrLen
    ; mov     [dwDispPos], edx

    call    DispMemSize     ; 显示内存信息

    jmp $   ; 进入死循环


; 启动分页机制 --------------------------------------------------------------
SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor edx, edx
    mov eax, [dwMemSize]
    mov ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
    div ebx
    mov ecx, eax    ; 此时 ecx 为页表的个数，也即 PDE 应该的个数
    test    edx, edx
    jz  .no_remainder
    inc ecx     ; 如果余数不为 0 就需增加一个页表
.no_remainder:
    mov [PageTableNumber], ecx  ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 首先初始化页目录
    mov ax, SelectorFlatRW
    mov es, ax
    mov edi, PageDirBase0   ; 此段首地址为 PageDirBase
    xor eax, eax
    mov eax, PageTblBase0 | PG_P  | PG_USU | PG_RWW
.1:
    stosd
    add eax, 4096       ; 为了简化, 所有页表在内存中是连续的.
    loop    .1

    ; 再初始化所有页表
    mov eax, [PageTableNumber]  ; 页表个数
    mov ebx, 1024       ; 每个页表 1024 个 PTE
    mul ebx
    mov ecx, eax        ; PTE个数 = 页表个数 * 1024
    mov edi, PageTblBase0   ; 此段首地址为 PageTblBase
    xor eax, eax
    mov eax, PG_P  | PG_USU | PG_RWW
.2:
    stosd
    add eax, 4096       ; 每一页指向 4K 的空间
    loop    .2

    mov eax, PageDirBase0
    mov cr3, eax
    mov eax, cr0
    or  eax, 80000000h
    mov cr0, eax
    jmp short .3
.3:
    nop

    ret
; 分页机制启动完毕 ----------------------------------------------------------

; Init8259A ---------------------------------------------------------------------------------------------
Init8259A:
    mov al, 011h
    out 020h, al    ; 主8259, ICW1.
    call    io_delay

    out 0A0h, al    ; 从8259, ICW1.
    call    io_delay

    mov al, 020h    ; IRQ0 对应中断向量 0x20（不再是实模式下的8了！）
    out 021h, al    ; 主8259, ICW2.
    call    io_delay

    mov al, 028h    ; IRQ8 对应中断向量 0x28
    out 0A1h, al    ; 从8259, ICW2.
    call    io_delay

    mov al, 004h    ; IR2 对应从8259
    out 021h, al    ; 主8259, ICW3.
    call    io_delay

    mov al, 002h    ; 对应主8259的 IR2
    out 0A1h, al    ; 从8259, ICW3.
    call    io_delay

    mov al, 001h
    out 021h, al    ; 主8259, ICW4.
    call    io_delay

    out 0A1h, al    ; 从8259, ICW4.
    call    io_delay

    mov al, 11111110b   ; 仅开启定时器中断
    out 021h, al    ; 主8259, OCW1.
    call    io_delay

    mov al, 11111111b   ; 屏蔽从8259所有中断
    out 0A1h, al    ; 从8259, OCW1.
    call    io_delay

    ret
; Init8259A ---------------------------------------------------------------------------------------------

io_delay:
    nop
    nop
    nop
    nop
    ret

SetTimer:
    mov al,34h          ; 设控制字值
    out 43h,al              ; 写控制字到控制字寄存器
    mov ax,1193182/100  ; 每秒100次中断（10ms一次）
    out 40h,al              ; 写计数器0的低字节
    mov al,ah               ; AL=AH
    out 40h,al              ; 写计数器0的高字节
    ret

; int handler ---------------------------------------------------------------
_ClockHandler:
ClockHandler    equ _ClockHandler - $$
    dec word [count]        ; 递减计数变量
    jnz LB_end          ; >0：跳转
    mov byte [gs:((80 * 0 + 40) * 2 + 1)], 0Ah  ; 0000: 黑底    1010: 绿字
    inc byte [gs:((80 * 0 + 40) * 2)]           ; 屏幕第 0 行, 第 40 列。
    mov word [count],delay  ; 重置计数变量=初值delay
LB_end:
    mov al, 20h
    out 20h, al             ; 发送 EOI
    iretd

_UserIntHandler:
UserIntHandler  equ _UserIntHandler - $$
    mov ah, 0Ch             ; 0000: 黑底    1100: 红字
    mov al, 'I'
    mov [gs:((80 * 0 + 70) * 2)], ax    ; 屏幕第 0 行, 第 70 列。
    iretd

_SpuriousHandler:
SpuriousHandler equ _SpuriousHandler - $$
    mov ah, 0Ch             ; 0000: 黑底    1100: 红字
    mov al, '!'
    mov [gs:((80 * 0 + 75) * 2)], ax    ; 屏幕第 0 行, 第 75 列。
    iretd

_DispStrHandler:
DispStrHandler equ _DispStrHandler - $$
    mov ah, bl              ; 字符颜色在bl中
    ; cx  ; string length
    ; esi ; string address
    mov edi, edx ; 光标起始位置在dx中
    ; mov edi, [dwDispPos] ;((80 * 4 + 61) * 2);
ContDisp:
    lodsb
    ; test al, al
    ; jz EndStr
    mov [gs:edi], ax
    add edi, 2
    ;jmp ContDisp
    dec cx
    cmp cx, 0
    jne ContDisp
EndStr:
    mov edi, edx
    ; mov [dwDispPos], edi
    iretd

; ---------------------------------------------------------------------------

; 显示内存信息 --------------------------------------------------------------
DispMemSize:
    push    esi
    push    edi
    push    ecx

    mov esi, MemChkBuf
    mov ecx, [dwMCRNumber]
                ; for(int i=0;i<[MCRNumber];i++)
; // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop:              ;{
    mov edx, 5      ;   for(int j=0;j<5;j++)    // 每次得到一个ARDS中的成员，共5个成员
    mov edi, ARDStruct; { // 依次显示：BaseAddrLow，BaseAddrHigh，LengthLow，LengthHigh，Type
.1:                 ;
    push    dword [esi] ;
    call    DispInt     ;       DispInt(MemChkBuf[j*4]); // 显示一个成员
    pop eax         ;
    stosd           ;       ARDStruct[j*4] = MemChkBuf[j*4];
    add esi, 4      ;
    dec edx         ;
    cmp edx, 0      ;
    jnz .1          ;   }
    call    DispReturn      ;   printf("\n");
    cmp dword [dwType], 1   ;   if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
    jne .2          ;   {
    mov eax, [dwBaseAddrLow]    ;
    add eax, [dwLengthLow]  ;
    cmp eax, [dwMemSize]    ;       if(BaseAddrLow + LengthLow > MemSize)
    jb  .2          ;
    mov [dwMemSize], eax    ;           MemSize = BaseAddrLow + LengthLow;
.2:                 ;   }
    loop    .loop           ;}
                    ;
    call    DispReturn      ;printf("\n");
    push    szRAMSize       ;
    call    DispStr         ;printf("RAM size:");
    add esp, 4          ;
                    ;
    push    dword [dwMemSize]   ;
    call    DispInt         ;DispInt(MemSize);
    add esp, 4          ;

    pop ecx
    pop edi
    pop esi
    ret
; ---------------------------------------------------------------------------

%include    "lib.inc"   ; 库函数

SegCode32Len    equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

